深入ES6 (二) set与map

Set

Set,是ES6提供的新的数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的值。

1
2
const set = new Set([1,2,3]);
console.log(typeof set)//object

Set只接受数组。

1
2
const set = new Set({"a":1});
console.log(set);//"TypeError: [object Object] is not iterable!"

Set自带去重功能

1
2
const set = new Set([1,2,3,4,4,4]);
console.log([...set])//[1,2,3,4]

Set的值的判断类似于”===”

1
2
const set = new Set([5,"5"]);
console.log([...set]);//[5,"5"]

set用法:

add(value):添加某个值,返回Set结构本身。

delete(value):删除某个值,返回一个布尔值,表示删除是否成功。

has(value):返回一个布尔值,表示该值是否为Set的成员。

clear():清除所有成员,没有返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
let set = new Set;
set.add(1);
console.log([...set]);// [1]
set.add(2);
console.log([...set]);// [1,2]
set.add(2);
console.log([...set]);// [1,2]
console.log(set.size);// 2
console.log(set.has(2));// true
set.delete(2);
console.log(set.has(2)); // false
set.clear();
console.log([...set]); //[]

其实Set本质就是一个给数组添加了很多方法的构造函数,所以Set也可以和arr有转换

1
2
3
4
5
6
const set = new Set([1,2,3,4,5]);
let arr = [];
arr = Array.from(set);
console.log(arr);//[1,2,3,4,5]
let turnSet = new Set(arr);
console.log(turnSet.size)//5

Set的遍历

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。

1
2
3
4
let set = new Set(["green","red","yellow"]);
for (let item of set) {
console.log(item);// red // green// blue
}

所以forEach也可以用

1
2
3
set.forEach((item,i) =>{
console.log(item)// red // green// blue
});

通过set去重

1
2
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];// [3, 5, 2]

总结

相对于arr数组来讲,Set就是一个给数组加了去重、是否存在等等功能的构造函数,
而这些功能其实数组通过js也可以实现,实际开发中也就has和去重对开发有实际性帮助,
而如果涉及到算法以及内存泄漏的话,WeakSet则需要重点学习一下,其用法百分之八十和Set一样,
其特点如其名,’weak’嘛,简易版Set,其目的只是为了避免内存泄漏。

Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。


为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

1
2
3
4
5
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"

Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

1
2
3
4
5
6
7
8
9
10
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

之所以Map可以接受一个数组作为传参,实际上为内部执行了下面的算法

1
2
3
4
5
6
7
8
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);

这还不算完,除此之外,我们还可以用Set生成Map。。。

1
2
3
4
5
6
7
8
9
10
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

如果对同一个键多次赋值,后面的值将覆盖前面的值。

1
2
3
4
5
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"

如果读取一个未知的键,则返回undefined。

1
new Map().get('asfddfsasadf')// undefined

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键

1
2
3
4
5
6
7
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

Map操作

size : 属性返回 Map 结构的成员总数。

set(key, value) : set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

get(key) : get方法读取key对应的键值,如果找不到key,返回undefined。

has(key) : has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

delete(key) : delete方法删除某个键,返回true。如果删除失败,返回false。

clear() : clear方法清除所有成员,没有返回值。

1
2
3
4
5
6
7
8
9
10
11
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.get('bar')//bar
map.has('bar')//true
map.delete('bar')
map.has('bar')//false
map.clear()
map.size // 0

Map的遍历 (需要特别注意的是,Map 的遍历顺序就是插入顺序。)

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

keys():返回键名的遍历器。

values():返回键值的遍历器。

entries():返回所有成员的遍历器。

forEach():遍历 Map 的所有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"

Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

Map数据结构转换

###(1)Map 转为数组: ###

上面提到了,直接用扩展运算符获取数组

1
2
3
4
5
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]


数组 转为 Map

将数组传入 Map 构造函数,就可以转为 Map。

1
2
3
4
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])

(3)Map 转为对象

1
2
3
4
5
6
7
8
9
10
11
12
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)

(4)对象转为 Map

1
2
3
4
5
6
7
8
9
10
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}

(5)Map 转为 JSON

Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。

1
2
3
4
5
6
7
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。

1
2
3
4
5
6
7
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'

(6)JSON 转为 Map

JSON 转为 Map,正常情况下,所有键名都是字符串。

1
2
3
4
5
6
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为 JSON 的逆操作。

1
2
3
4
5
6
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

总结

Map实则也是一个构造函数,其优点在于处理非字符串(如Dom节点)的json遍历,配合for…of后,其功能和方法都还算不错,总体来讲Map的功能还是蛮强大的。